import sys, wx, xmlrpclib, socket, os.path, os, urllib, time, platform, copy
import traceback as tb
from math import ceil, floor
from threading import Thread, RLock
from managerConn import ManagerConnection
import subprocess as sp
import getpass

DO_RUN = True
FILE_SERVER_PORT = 8800  # for the file server
FILE_SERVER_BIN_PORT = 8802
APP_LAUNCHER_PORT = 19010
DEFAULT_TIMEOUT = None  # no timeout because server side processing might take a while
PREVIEW_SIZE = (150,150)
ALLOW_SHARED = False  # can we upload files to multiple sites?
DEFAULT_SAGE_HOST = "bigdaddy.evl.uic.edu"
SHARE_VNC = False
SCREENSHOT_DIR = os.path.expanduser("~")
SCREENSHOT_NAME = "Screen shot"

config = None
firstMsgTime = -1.0
mCapture = None
wCapture = None
fileServer = None
appLauncher = None
sharedSite = None
frame = None

CAPTURED = 11
RELEASED = 12
QUIT = 13
CHANGE_EDGE = 14
DISABLE_CORNERS = 15

LEFT_EDGE   = 1
RIGHT_EDGE  = 2
TOP_EDGE    = 3
BOTTOM_EDGE = 4
edgeNames= ["Left", "Right", "Top", "Bottom"]
toVSpace = False
group_name = None
group_count = 0

opj = os.path.join


def getFileServer(host=None):
    if not host:
        return fileServer
    else:
        return FileServer(host, FILE_SERVER_PORT)

def setFileServer(host):
    global fileServer
    fileServer = FileServer(host, FILE_SERVER_PORT)


def getAppLauncher(host=None):
    if not host:
        return appLauncher
    else:
        return xmlrpclib.ServerProxy("http://"+host+":"+str(APP_LAUNCHER_PORT))

def setAppLauncher(host):
    global appLauncher
    appLauncher = xmlrpclib.ServerProxy("http://"+host+":"+str(APP_LAUNCHER_PORT))


def startApp(appName, pos=False, size=False, oa="", host=None):
    global toVSpace 
    if toVSpace == True : 
        return

    #sageW = 4080
    #sageH = 2304
    # app appLauncher connection
    #centerX = int( sageW/2.0-size[0]/2.0 )
    #centerY = int( sageH/2.0-size[1]/2.0 )
    if not host:
        host = config.sageHost
    if toVSpace == True : 
        oa += " vs"
    return getAppLauncher(host).startDefaultApp(appName, host, 20002, False, "default", pos, size, oa)
            

def BoldFont(window = None):
    if window == None:
        ss = wx.SystemSettings
        ssFont = ss.GetFont(wx.SYS_DEFAULT_GUI_FONT)
        ssFont.SetWeight(wx.BOLD)
        return ssFont
    else:
        winFont = window.GetFont()
        winFont.SetWeight(wx.BOLD)
        return winFont

def stopCapture():
    global wCapture, winCapturePipe
    if sys.platform.startswith("darwin"):
        if mCapture:
            mCapture.stdin.write(str(QUIT) + " 1\n")  # send a message 
            mCapture.stdin.flush()
            mCapture.stdin.close()
            mCapture.wait()
    elif sys.platform.startswith("win"):   # windows
        SHARED_DO_RUN.value = 0
        wCapture.join()


def stop():
    print "stop is called.... "
    global DO_RUN
    DO_RUN = False
    stopCapture()
    config.save()
    manager.quit()
    wx.GetApp().GetTopWindow().Destroy()
    wx.GetApp().ExitMainLoop()


def onPtrCaptured():
    global firstMsgTime
    firstMsgTime = time.time()
    wx.GetApp().GetTopWindow().showSplash()


def onPtrReleased():
    manager.disconnect()
    #if sys.platform != "darwin" and platform.platform().startswith("Windows-7"):
    #    wCapture.restart() # a hack for Windows 7
    wx.GetApp().GetTopWindow().hideSplash()
    

def changeCaptureEdge():
    if sys.platform.startswith("darwin"):
        if mCapture:
            mCapture.stdin.write( str(CHANGE_EDGE)+" "+str(config.captureEdge)+"\n")  # send a message 
            mCapture.stdin.write( str(DISABLE_CORNERS)+" "+str(config.disableCorners)+"\n")  # send a message 
            mCapture.stdin.flush()
    elif sys.platform.startswith("win"):
        global SHARED_CAPTURE_EDGE, SHARED_DISABLE_CORNERS
        SHARED_CAPTURE_EDGE.value = config.captureEdge
        SHARED_DISABLE_CORNERS.value = config.disableCorners
        

def getConfig():
    global config
    return config


def Message(msg, title):
    dlg = wx.MessageDialog(None, msg, title, style = wx.OK)
    dlg.ShowModal()
    


COLOR = 4
TWO_FINGERS = 6
THREE_FINGERS = 7
HAS_DOUBLE_CLICK = 10



def sendInitialMsg(*data):
    msg = ""
    for m in data:
        msg+=str(m)+" "
    manager.initialMsg(config.ptrLabel, "mouse", msg)


def sendMsg(*data):
    try:
        global firstMsgTime
        now = time.time()
        
        msg = ""
        for m in data:
            msg+=str(m)+" "

        if not manager.sendMessage(config.ptrLabel, "mouse", msg):
            firstmsgTime = -1.0
        
        if firstMsgTime > 0.0 and now-firstMsgTime > 0.8:
            firstMsgTime = -1.0
            if not manager.sendMessage(config.ptrLabel, "mouse", str(COLOR) + " " + 
                                       str(config.ptrColor.Red()) + " " +
                                       str(config.ptrColor.Green()) + " " +
                                       str(config.ptrColor.Blue())):
                firstMsgTime = time.time()
    except:
        tb.print_exc()



""" We need this on Windows because hooks time out if the device event doesn't 
    get processed quickly enough... so we collect all the events here and 
    send them from a separate thread so that the event callback can return quickly. 
"""
class MessageSender:
    def __init__(self):
        self.msgLock = RLock()
        self.msgQueue = []   # combined  messages (list of string messages)
        self.msgFreq = 100    # send messages roughly 100 times a sec... if any
        
        # start the  sender thread
        t = Thread(target=self.runThread)
        t.start()


    def runThread(self):
        global DO_RUN
        while DO_RUN:
            
            # grab the first message from the queue... if any
            msg = ""
            self.msgLock.acquire()
            if len(self.msgQueue) > 0:
                msg = self.msgQueue.pop(0)
            self.msgLock.release()

            # send the message if there is something to send
            msg = msg.strip()
            if msg != "":
                sendMsg(msg)

            # sleep for a certain time
            time.sleep(1.0/self.msgFreq)


    def queueMsg(self, *data):
        """ this actually puts the messages in a queue
        which are then sent at fixed intervals """

        # first assemble the data into a string
        msg = ""  
        for d in data:   
            msg += " "+str(d)

        self.msgLock.acquire()
        self.msgQueue.append(msg)
        self.msgLock.release()

 


class FileServer:
    
    def __init__(self, host, port):
        self.host = host  #where the FileLibrary is running
        self.port = str(port)
        self.connected = False
        self.Connect()


        # try to connect to the server
        # if we failed, set the flag
        # if we succeed, get the path where the images are stored so that we can send it to the app
    def Connect(self):
        if self.connected: return True

        print "\nConnecting to XMLRPC server at: http://"+str(self.host)+":"+self.port
        socket.setdefaulttimeout(1)
        self.server = xmlrpclib.ServerProxy("http://"+str(self.host)+":"+self.port)
        try:
            bla = self.server.TestConnection()[1]  #just see if the connection opened correctly
            socket.setdefaulttimeout(DEFAULT_TIMEOUT)
        except socket.timeout:
            print "Could not connect to the file server at: "+str(self.host)+":"+self.port
            self.connected = False
            return False
        except:
            tb.print_exc()
            print "Could not connect to the file server at: "+str(self.host)+":"+self.port
            self.connected = False
            return False
        else:
            print "Connected to the XMLRPC server at: http://"+str(self.host)+":"+self.port
            self.connected = True
            return True


    def IsConnected(self):
        return self.Connect()
            

        # uploads the file to the file library and returns information about the file
    def UploadFile(self, fullPath):
        # hyejung
        global toVSpace

        try:
            # get the file info first
            fileInfo = self.GetNewFileInfo(fullPath)
            if not fileInfo: return False            # file type not supported

            fileExists = fileInfo[5]
            fileType = fileInfo[0]

            #if fileExists and toVSpace == False:                           # if the file exists on the server, just show it (ie. dont send it)
            #    return fileInfo #no need to upload
            #else:
            if True :
                if fullPath.startswith("http"):   # URL
                    res = self.server.UploadLink(fullPath)
                    if fileType == "video":   # video link doesn't have a recognizable file type from the url so the file server will return the path to the whole downloaded video file
                        fullPath = res
                else:
                    res = self.__SendFile(fullPath)    # did upload fail for some reason?                   

                if res:
                    return self.GetNewFileInfo(fullPath)   # this forces the creation of thumbnail
                else:                           
                    return False                     # upload failed so whatever you are doing after this (ShowFile maybe), don't do it

        except socket.error: 
            self.connected = False
            Message("Unable to upload file. There is no connection with the File Server.", "No connection")
            return False
        except xmlrpclib.ProtocolError:
            self.connected = False
            print "Protocol error"
            return False
    

        # gets the information about the file and the supporting app from the file library
    def GetNewFileInfo(self, fullPath):
        (path, filename) = os.path.split(fullPath)
        filename = "_".join(filename.split())
        filename = filename.replace("(", "_")
        filename = filename.replace(")", "_")

        # check if the file type is supported
        # if it is, it will get some data about the file (type, viewerApp ...)
        try:
            fileSize = os.stat(fullPath).st_size  #get the size in bytes
        except:
            fileSize = -1

        fileInfo = self.server.GetFileInfo( filename, fileSize )
        if not fileInfo:
            extension = os.path.splitext(filename)[1].lower()
            Message("File type <"+extension+"> not supported.", "Invalid File Type")
            return False

        return fileInfo


    def __SendFile(self, fullPath):
        print "send file is called ****** "
        global toVSpace
        global group_name

        doDlg = False
        try:
            s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
            s.connect((self.host, FILE_SERVER_BIN_PORT))

            #convert to a filename with _ instead of empty spaces
            convertedFilename = "_".join(os.path.basename(fullPath).split())
            convertedFilename = convertedFilename.replace("(", "_")
            convertedFilename = convertedFilename.replace(")", "_")

            # send the header first
            fileSize = os.path.getsize(fullPath)
            header = convertedFilename + " " + str(PREVIEW_SIZE[0]) + " " + str(PREVIEW_SIZE[1]) + \
                     " " + str(fileSize) + " " + str(toVSpace) + " " + str(group_name) + "\n"
            print "header----", header
            s.sendall(header)     

            # show the progress dialog if file is bigger
            if fileSize > 200000:
                dlg = wx.ProgressDialog("Uploading "+os.path.basename(fullPath), "Uploading... (0/%.2f)"%(fileSize/1048576), \
                                            fileSize, \
                                        parent=None, \
                                        style=wx.PD_SMOOTH | wx.PD_CAN_ABORT |
                                        wx.PD_ELAPSED_TIME | wx.PD_REMAINING_TIME | wx.PD_AUTO_HIDE)
                doDlg = True
                
            # send the file data
            lastTime = time.time()
            f=open(fullPath, "rb")
            t = 0
            for line in f:
                s.sendall(line)
                t += len(line)

                # update the dialog if needed
                if doDlg and time.time()-lastTime > 0.3:
                    lastTime = time.time()
                    (cont, skip) =  dlg.Update(t, "Uploading... (%.2f/%.2f MB)"%
                                               (float(t)/1048576, float(fileSize)/1048576))
                    if not cont:
                        s.close()
                        f.close()
                        dlg.Destroy()
                        return False

            if doDlg:  dlg.Destroy()
                
            s.sendall("\n")  # because the other side is reading lines
            f.close()
            s.recv(1)
        except:
            print "Error sending file to File Server: ", sys.exc_info()[0], sys.exc_info()[1]
            if doDlg:  dlg.Destroy()
            Message("Could not upload file "+str(os.path.basename(fullPath)), "Error")
            return False

        return True
                
 


### the base class for accepting the dropped files
class FileDropTarget(wx.PyDropTarget):
    def __init__(self):
        self.do = wx.DataObjectComposite()
        self.filedo = wx.FileDataObject()
        self.textdo = wx.TextDataObject()
        self.do.Add(self.textdo, preferred=True)
        self.do.Add(self.filedo)
        wx.PyDropTarget.__init__(self, self.do)
        

    def OnData(self, x, y, d):
        global group_name 
        global group_count 
        global toVSpace

        if not getFileServer().IsConnected():
            Message("Unable to upload/show file. There is no connection with the File Server.", "No connection")
            return False
        
        self.GetData() #get the dropped data

        if self.do.GetReceivedFormat() == wx.DF_FILENAME:
            url = self.textdo.GetText()
            if url: 
                return [url]
            else:
                files = self.filedo.GetFilenames()  #extract the filenames from the dropped data
                # hyejung
                if len(files) > 1 : 
                    if toVSpace and group_name != None : 
                        group_name = getpass.getuser()
                        group_name += "-" + str(group_count)
		        group_count +=1 
                for f in files:
                    if os.path.isdir(f):
                        Message("You cannot upload directories. Only individual files.", "Upload Failed")
                        return False
                    else:
                        return files

        else:
            try:
                url = self.textdo.GetText()
                return [url]
            except:
                Message("Error downloading image using the link", "Image download failed")
                return False
        
            
 
class DropTarget(FileDropTarget):
    def __init__(self):
        FileDropTarget.__init__(self) # dont provide library location right away since we will let the user choose that each time


        # called by the system
    def OnData(self, x, y, d):   # just upload the file to the libary on the machine we are connected to
        files = FileDropTarget.OnData(self,x,y,d)
        self.FilesDropped(files)
        return d


        # can be called manually... like we do when files are dropped on Dock icon
    def FilesDropped(self, files, deleteAfterUpload=False):
        if files:
            t = Thread(target=self.__StartShow, args=[files, deleteAfterUpload])
            t.start()

    
    # runs in a thread, showing all dropped files a few seconds apart
    def __StartShow(self, files, deleteAfterUpload):
        for f in files:
            wx.CallAfter(self.__UploadAndShow, f, deleteAfterUpload)
            time.sleep(2)


    # first uploads the file and then shows it
    def __UploadAndShow(self, f, deleteAfterUpload):
        fileInfo = getFileServer().UploadFile(f)

        # in case we are uploading screenshots, delete them afterwards...
        if deleteAfterUpload: 
            try:
                os.remove(f)
            except:
                tb.print_exc()  # do nothing if file cant be deleted...

        if fileInfo:  #if upload succeeded
            # hyejung
            self.__ShowFile(fileInfo)
        else:
            Message("Sending file to SAGE failed... "+f, "Upload failed")
    
        if sharedSite:
            #print "Also sharing with: ", sharedSite
            fileInfo = getFileServer(sharedSite).UploadFile(f)

            if fileInfo:  #if upload succeeded
                self.__ShowFile(fileInfo, sharedSite)
            else:
                Message("Sending file to Shared SAGE site failed... "+f, "Upload failed")
              

    # shows one file
    def __ShowFile(self, fileInfo, dest=None):
        # hyejung
        fileType, size, fullRemotePath, appName, params, fileExists = fileInfo
        print "showfile", params

        if fileType == "image":
            res = startApp(appName, oa=fullRemotePath+" "+params, host=dest)
        elif fileType == "video" or fileType == "pdf":
            res = startApp(appName, oa=params+" "+fullRemotePath, host=dest)
        else:  #for other types
            res = startApp(appName, oa=fullRemotePath+" "+params, host=dest)

        if res == -1:
            Message("Application not started. Either application failed, the application launcher is not running or the application  <<"+appName+">>  is not configured in application launcher.", "Application Launch Failed")


class Frame(wx.Frame):
    def __init__(self):
        wx.Frame.__init__(self, None, -1, "SAGE pointer")
        self.Bind(wx.EVT_CLOSE, self.onClose)
        self.SetMinSize((220,220))
        self.panel = wx.Panel(self, -1)

        #self.dispLabel = wx.StaticText(self.panel, -1, "Hold Caps Lock then press Tab to control  <<< "+config.sageHost+" >>>")
        self.dispLabel = wx.StaticText(self.panel, -1, "Move cursor to "+edgeNames[config.captureEdge-1].upper()+" edge to control: "+config.sageHost)
        self.dispLabel.SetFont(BoldFont())

        shareBtn = wx.Button(self.panel, -1, "Share Desktop")
        shareBtn.Bind(wx.EVT_BUTTON, self.onShareBtn)

        #mediaBtn = wx.Button(self.panel, -1, "Clear All")
        #mediaBtn.Bind(wx.EVT_BUTTON, self.onClearAllBtn)

        horSizer = wx.BoxSizer(wx.HORIZONTAL)
        horSizer.Add(self.dispLabel, 0, wx.ALIGN_CENTER | wx.ALL, border=10)
        horSizer.Add(shareBtn, 0, wx.ALIGN_CENTER | wx.ALL, border=10)

        self.sizer = wx.BoxSizer(wx.VERTICAL)
        self.sizer.Add(horSizer, 0, wx.ALIGN_CENTER, border=0)

        self.toVS = wx.CheckBox(self.panel, -1, 'Send to Browser -not show on display')
        self.panel.Bind(wx.EVT_CHECKBOX, self.onVirtualSpace, self.toVS)
        self.sizer.Add(self.toVS, 0, wx.ALIGN_LEFT, border=0)

        self.group = wx.CheckBox(self.panel, -1, 'Send files as a Group')
        self.panel.Bind(wx.EVT_CHECKBOX, self.onGroup, self.group)
        self.sizer.Add(self.group, 0, wx.ALIGN_LEFT, border=0)

        self.sizer.Add(self.makeDropPanel(), 1, wx.EXPAND | wx.ALIGN_CENTER | wx.ALL, border=2)

        if ALLOW_SHARED: self.sizer.Add(self.makeShareComboBox(), 0, wx.ALIGN_RIGHT | wx.ALL, border=2)
        self.panel.SetSizer(self.sizer)
        self.panel.Fit()
        self.Fit()

        self.splashBmp = wx.Bitmap("splash_small.png", wx.BITMAP_TYPE_PNG)
        self.splashWin = None
    
        self.SetMenuBar(self.makeMenu())

        pos = self.GetPosition()
        size = self.GetSize()
        pos[1] += size[1]
        size = (600, 400)

 
    def makeMenu(self):
        menuBar = wx.MenuBar()
        ptrMenu = wx.Menu()
        sageMenu = wx.Menu()

        miLabel = ptrMenu.Append(-1, "Change Label", "Change Pointer Label")
        miColor = ptrMenu.Append(-1, "Change Color", "Change Pointer Color")
        miSAGE = sageMenu.Append(-1, "Control Different Display", "Connect to which SAGE?")
        miEdge = sageMenu.Append(-1, "Change Shared Screen Edge", "Which edge of your screen connects to SAGE?")

        self.Bind(wx.EVT_MENU, self.onLabelChangeMenuItem, miLabel)
        self.Bind(wx.EVT_MENU, self.onColorChangeMenuItem, miColor)
        self.Bind(wx.EVT_MENU, self.onSAGEChangeMenuItem, miSAGE)
        self.Bind(wx.EVT_MENU, self.onEdgeChangeMenuItem, miEdge)

        menuBar.Append(sageMenu, "SAGE")
        menuBar.Append(ptrMenu, "Pointer")
        return menuBar

    def onLabelChangeMenuItem(self, event):
        l = wx.GetTextFromUser("Pointer label", "Pointer Label", centre=False)
        config.ptrLabel = "%".join(l.split())
        

    def onEdgeChangeMenuItem(self, event):
        dlg = wx.Dialog(self, -1, 'Shared Screen Edge')
        
        l = wx.StaticText(dlg, -1, "Which screen edge will share\nthe pointer to SAGE?")
        ch = wx.Choice(dlg, -1, (100, 50), choices = edgeNames)
        ch.SetStringSelection( edgeNames[config.captureEdge-1] )
        cb = wx.CheckBox(dlg, -1, "Disable corners", style=wx.ALIGN_RIGHT)
        cb.SetValue(bool(config.disableCorners))
        
        s = wx.BoxSizer(wx.VERTICAL)
        s.Add(l, 0, wx.ALIGN_CENTER | wx.ALL, border=5)
        s.Add(ch, 0, wx.ALIGN_CENTER | wx.ALL, border=5)
        s.Add(cb, 0, wx.ALIGN_CENTER | wx.ALL, border=5)
        s.Add(dlg.CreateButtonSizer(wx.OK), 0, wx.ALIGN_CENTER | wx.ALL, border=10)
        dlg.SetSizer(s)
        dlg.Fit()
        if dlg.ShowModal() == wx.ID_OK:
            config.disableCorners = int(cb.GetValue())
            config.captureEdge = ch.GetSelection()+1
            self.dispLabel.SetLabel("Move cursor to "+edgeNames[config.captureEdge-1].upper()+" edge to control: "+config.sageHost)
        dlg.Destroy()
        changeCaptureEdge()


    def onSAGEChangeMenuItem(self, event):
        def onOK(event):
            newHost = hostsBox.GetValue()
            if newHost != "":
                tokens = "%".join(newHost.split()).split(":")
                config.sageHost = newHost   
                justHost = tokens[0]

                if newHost not in config.savedSageHosts:
                    config.savedSageHosts.append(newHost)
                self.dispLabel.SetLabel("Move cursor to "+edgeNames[config.captureEdge-1].upper()+" edge to control: "+config.sageHost)
                self.sizer.Layout()
                if len(tokens) == 2:
                    manager.changeHost(justHost, int(tokens[1]) )
                else:
                    manager.changeHost(config.sageHost)

                #if not sys.platform.startswith("win"):
                time.sleep(0.5)
                #wx.SafeYield(None, True)
                if not manager.connected():
                    time.sleep(1)
                    #wx.SafeYield(None, True)
                    if not manager.connected():
                        connectionMsg.SetLabel("No connection to SAGE on: "+newHost)
                        dlg.Layout()
                        dlg.Fit()
                        return

                setFileServer(justHost)
                setAppLauncher(justHost)
            dlg.EndModal(dlg.GetReturnCode())

        def onText(event):
            connectionMsg.SetLabel("")

        def onCancel(event):
            dlg.EndModal(dlg.GetReturnCode())

        def onDelete(event):
            newHost = hostsBox.GetValue()
            if newHost in config.savedSageHosts:
                config.savedSageHosts.remove(newHost)
                i = hostsBox.FindString(newHost)
                if i != wx.NOT_FOUND:
                    hostsBox.Delete(i)
                    if len(config.savedSageHosts) > 0:
                        hostsBox.SetValue(config.savedSageHosts[0])
                    else:
                        hostsBox.SetValue("")
            connectionMsg.SetLabel("")
                    

        dlg = wx.Dialog(self, -1, "SAGE Connection")

        t = wx.StaticText(dlg, -1, "Connect to SAGE host (IP or hostname)")

        connectionMsg = wx.StaticText(dlg, -1, "")
        connectionMsg.SetForegroundColour("Red")

        hostsBox = wx.ComboBox(dlg, -1, config.sageHost, size=(200,-1), choices=config.savedSageHosts, style=wx.CB_DROPDOWN)
        hostsBox.Bind(wx.EVT_TEXT, onText)
        hostsBox.Bind(wx.EVT_COMBOBOX, onText)

        delBtn = wx.Button(dlg, -1, 'Delete')
        delBtn.Bind(wx.EVT_BUTTON, onDelete)

        horSizer = wx.BoxSizer(wx.HORIZONTAL)
        horSizer.Add(hostsBox, 0, wx.ALIGN_LEFT | wx.ALL, 5)        
        horSizer.Add(delBtn, 0, wx.ALIGN_LEFT | wx.ALL, 5)
                            
        btns = dlg.CreateButtonSizer(wx.CANCEL | wx.OK)
        dlg.Bind(wx.EVT_BUTTON, onOK, id=wx.ID_OK)
        dlg.Bind(wx.EVT_BUTTON, onCancel, id=wx.ID_CANCEL)

        sizer = wx.BoxSizer(wx.VERTICAL)
        sizer.Add(t, 0, wx.ALIGN_LEFT | wx.LEFT | wx.TOP, 10)
        sizer.Add(horSizer, 0, wx.ALIGN_LEFT | wx.ALL, 5)
        sizer.Add(connectionMsg, 0, wx.ALIGN_CENTER | wx.BOTTOM | wx.LEFT | wx.RIGHT, 5)
        sizer.Add(btns, 0, wx.ALIGN_CENTER | wx.ALL, 5)

        dlg.SetSizer(sizer)
        dlg.Fit()
        dlg.ShowModal()



    def onColorChangeMenuItem(self, event):
        config.ptrColor = wx.GetColourFromUser(self, config.ptrColor, "Set Pointer Color")

    def onVirtualSpace(self, event):
        global toVSpace 
        toVSpace = self.toVS.IsChecked()

    def onGroup(self, event):
        global group_name 
        flag = self.group.IsChecked()
        if flag == False :
            group_name = None 
        else : 
            group_name = getpass.getuser()

    
    def makeShareComboBox(self):
        t = wx.StaticText(self.panel, -1, "Also share with: ")

        sharedSites = [""]
        sharedSites.extend( config.sharedSageHosts.keys() )

        # This combobox is created with a preset list of values.
        cb = wx.ComboBox(
            self.panel, wx.ID_ANY, "", choices=sharedSites, 
            style=wx.CB_DROPDOWN | wx.CB_READONLY)
        #cb.SetSize((20,40))
        
        cb.Bind(wx.EVT_COMBOBOX, self.onShareComboBox)

        sz = wx.BoxSizer(wx.HORIZONTAL)
        sz.Add(t, 0, wx.ALIGN_CENTER | wx.RIGHT, border=4)
        sz.Add(cb, 0, wx.ALIGN_CENTER | wx.RIGHT, border=8)

        return sz


    def onShareComboBox(self, event):
        global sharedSite

        site = event.GetString()
        if site != "":
            siteHost = config.sharedSageHosts[site]
            if siteHost == config.sageHost:
                sharedSite = None  # dont use the same one we are already using...
            else:
                sharedSite = siteHost
        else:
            sharedSite = None
          

    def makeDropPanel(self):
        dropPanel = wx.Panel(self.panel, style=wx.BORDER_SIMPLE)
        
        l = wx.StaticText(dropPanel, -1, "Drop multimedia files here", style=wx.ALIGN_CENTRE | wx.ST_NO_AUTORESIZE)
          
        s = wx.BoxSizer(wx.HORIZONTAL)
        s.Add(l, 1, wx.ALIGN_CENTER | wx.ALL, border=0)
        dropPanel.SetSizer(s)

        self.dt = DropTarget()
        l.SetDropTarget(self.dt)

        dt1 = DropTarget()
        dropPanel.SetDropTarget(dt1)

        return dropPanel


    def offerScreenshotUpload(self, files):
        dlg = wx.Dialog(self, -1, "Send the screenshot to SAGE?", style=wx.STAY_ON_TOP | wx.DEFAULT_DIALOG_STYLE)
        m = wx.StaticText(dlg, -1, "Send the screenshot to SAGE?")

        s = dlg.CreateButtonSizer(wx.OK | wx.CANCEL)                
        s2 = wx.BoxSizer(wx.VERTICAL)
        s2.Add(m, 0, wx.LEFT | wx.RIGHT | wx.TOP | wx.ALIGN_CENTER, 10)
        s2.Add(s, 0, wx.ALL | wx.ALIGN_CENTER, 5)
        dlg.SetSizer(s2)

        dlg.Fit()
        dlg.CenterOnScreen()
        self.RequestUserAttention()
        res = dlg.ShowModal()

        if res == wx.ID_OK:
            self.dt.FilesDropped(files, deleteAfterUpload=True)


    def showSplash(self):
        self.splashWin = wx.SplashScreen(self.splashBmp, wx.SPLASH_NO_TIMEOUT, 0, self, wx.ID_ANY, (0,0))
        self.splashWin.Show()


    def hideSplash(self):
        if self.splashWin:
            self.splashWin.Close()
            self.splashWin = None

    def onShareBtn(self, evt):
        myIP = socket.gethostbyname(socket.gethostname())
        displayNum = "0"
        (w,h) = wx.DisplaySize()

        # new dialog
        dlg = wx.Dialog(self, -1, "Your VNC Connection")

        h0 = wx.GridSizer(6,3, 5,5)
        
        exp1_label = wx.StaticText(dlg, -1, "Required")
        exp1_label.SetBackgroundColour(wx.Colour(200,200,200))
        label_font = exp1_label.GetFont()
        label_font.SetStyle(wx.FONTSTYLE_ITALIC)
        exp1_label.SetFont(label_font)
        exp1_label.SetToolTip(wx.ToolTip("A password is required to connect to your VNC server"))
        
        h0.Add(exp1_label, 0, wx.ALIGN_CENTER_VERTICAL | wx.ALL, border=2)
        dlg_pwd_label = wx.StaticText(dlg, -1, "Password: ")
        dlg_pwd_label.SetToolTip(wx.ToolTip("Enter your VNC password (login password if your VNC server uses user authentication)"))
        dlg_pwd_entry = wx.TextCtrl(dlg, -1, "", style=wx.TE_PASSWORD)
        dlg_pwd_entry.SetToolTip(wx.ToolTip("Enter your VNC password (login password if your VNC server uses user authentication)"))
        h0.Add(dlg_pwd_label, 0, wx.ALIGN_CENTER_VERTICAL | wx.ALL, border=2)
        h0.Add(dlg_pwd_entry, 0, wx.ALIGN_CENTER_VERTICAL | wx.ALL, border=2)
        
        h0.Add(wx.StaticLine(dlg), 0, wx.ALIGN_CENTER_VERTICAL | wx.ALL, border=0)
        dlg_isuser_label = wx.StaticText(dlg, -1, "Use username: ")
        dlg_isuser_label.SetToolTip(wx.ToolTip("Select for MacOSX Lion and servers with user login"))
        dlg_isuser_cb = wx.CheckBox(dlg, -1, label="",)
	dlg_isuser_cb.SetValue(False)
        dlg_isuser_cb.SetToolTip(wx.ToolTip("Select for MacOSX Lion and servers with user login"))
        h0.Add(dlg_isuser_label, 0, wx.ALIGN_CENTER_VERTICAL | wx.ALL, border=2)
        h0.Add(dlg_isuser_cb, 0, wx.ALIGN_CENTER_VERTICAL | wx.ALL, border=2)
        #h0.Add(wx.StaticLine(dlg), 0, wx.ALIGN_CENTER_VERTICAL | wx.ALL, border=0)
        #h0.Add(wx.StaticLine(dlg), 0, wx.ALIGN_CENTER_VERTICAL | wx.ALL, border=0)
        #h0.Add(wx.StaticLine(dlg), 0, wx.ALIGN_CENTER_VERTICAL | wx.ALL, border=0)
 
        exp1_label = wx.StaticText(dlg, -1, "Optional")
        exp1_label.SetToolTip(wx.ToolTip("Optional parameters to connect to your VNC server"))
        exp1_label.SetFont(label_font)
        exp1_label.SetBackgroundColour(wx.Colour(200,200,200))
        h0.Add(exp1_label, 0, wx.ALIGN_CENTER_VERTICAL | wx.ALL, border=2)
        dlg_ip_label = wx.StaticText(dlg, -1, "Your IP address: ")
        dlg_ip_label.SetToolTip(wx.ToolTip("Select the public IP address of your machine"))
        # dlg_ip_entry = wx.TextCtrl(dlg, -1, myIP)
        # get the list of current IP addresses of the user's machine
        list_ips = socket.gethostbyname_ex(socket.gethostname())[2]
        local_ip = manager.local_ip()

        dlg_ip_entry = wx.ComboBox(dlg, -1, value=local_ip, choices=list_ips, style=wx.CB_DROPDOWN)
        dlg_ip_entry.SetToolTip(wx.ToolTip("Select the public IP address of your machine"))
        h0.Add(dlg_ip_label, 0, wx.ALIGN_CENTER_VERTICAL | wx.ALL, border=2)
        h0.Add(dlg_ip_entry, 0, wx.ALIGN_CENTER_VERTICAL | wx.ALL, border=2)

        dlg_dsp_label = wx.StaticText(dlg, -1, "Display number: ")
        dlg_dsp_label.SetToolTip(wx.ToolTip("Select the display number running VNC (0 is default)"))
        dlg_dsp_entry = wx.TextCtrl(dlg, -1, "0")
        dlg_dsp_entry.SetToolTip(wx.ToolTip("Select the display number running VNC (0 is default)"))
        h0.Add(wx.StaticText(dlg, -1, ""))
        h0.Add(dlg_dsp_label, 0, wx.ALIGN_CENTER_VERTICAL | wx.ALL, border=2)
        h0.Add(dlg_dsp_entry, 0, wx.ALIGN_CENTER_VERTICAL | wx.ALL, border=2)

        dlg_user_label = wx.StaticText(dlg, -1, "Username: ")
        dlg_user_label.SetToolTip(wx.ToolTip("Type your user name (if your VNC server requires it)"))
        dlg_user_entry = wx.TextCtrl(dlg, -1, str(os.getenv("USER")))
        dlg_user_entry.SetToolTip(wx.ToolTip("Type your user name (if your VNC server requires it)"))
        h0.Add(wx.StaticText(dlg, -1, ""))
        h0.Add(dlg_user_label, 0, wx.ALIGN_CENTER_VERTICAL | wx.ALL, border=2)
        h0.Add(dlg_user_entry, 0, wx.ALIGN_CENTER_VERTICAL | wx.ALL, border=2)
        
        s = wx.BoxSizer(wx.VERTICAL)
        s.Add(h0, 0, wx.ALIGN_CENTER | wx.ALL, border=2)
        s.Add(dlg.CreateButtonSizer(wx.OK | wx.CANCEL), 0, wx.ALIGN_CENTER | wx.ALL, border=10)
        dlg.SetSizer(s)
        dlg.Fit()

        if dlg.ShowModal() != wx.ID_CANCEL:
            # use username or not
            isuser = dlg_isuser_cb.GetValue()
            # display IP
            di = dlg_ip_entry.GetValue()
            # display number
            dn = dlg_dsp_entry.GetValue()
            # username
            du = dlg_user_entry.GetValue()
            # pwd
            dp = dlg_pwd_entry.GetValue()
            if not dp:
                dp = '""'

            if isuser:
              args = " -s " + di + " -d " + dn + " -p " + dp + " -u " + du + " -r 10"
            else:
              args = " -s " + di + " -d " + dn + " -p " + dp + " -r 10"
            print("Command> " + args)
            startApp("VNCViewer", (100,100), (w,h), args)
            if SHARE_VNC and sharedSite:
                startApp("VNCViewer", (100,100), (w,h), args, sharedSite)

        dlg.Destroy()

        # dlg = wx.PasswordEntryDialog(self, "Your VNC Password:")
        # if dlg.ShowModal() != wx.ID_CANCEL:
        #     pwd = dlg.GetValue()
        #     args = myIP+" 0 "+str(w)+" "+str(h)+" "+pwd
        #     startApp("VNCViewer", (100,100), (w,h), args)
        #     if SHARE_VNC and sharedSite:
        #         startApp("VNCViewer", (100,100), (w,h), args, sharedSite)
        # dlg.Destroy()


    def onClose(self, evt):
        stop()

        
class MyApp(wx.App):
    def __init__(self, *args, **kwargs):
        wx.App.__init__(self, *args, **kwargs)
        self.__dropped = []  # a list of files that were dropped
        self.Bind(wx.EVT_TIMER, self.ProcessDroppedFiles)
        self.timer = wx.Timer(self)


    def MacOpenFile(self, filename):
        """Called for files droped on dock icon, or opened via finders context menu"""
        self.__dropped.append(filename)
        self.StartTimer()


    def MacOpenURL(self, url):
        self.__dropped.append(url)
        self.StartTimer()
        
    
    def StartTimer(self):
        if not self.timer.IsRunning():
            self.timer.Start(milliseconds=200, oneShot=True)


    def ProcessDroppedFiles(self, event):
        self.GetTopWindow().dt.FilesDropped(copy.copy(self.__dropped))
        self.__dropped = []


# ====================   SAVED CONFIGURATION   ==================================

class Config:

    def __init__(self):
        self.ptrColor = wx.Colour(230,26,26)
        self.ptrLabel = ""
        self.sageHost = ""
        self.sharedSageHosts = {"EVL Cybercommons":DEFAULT_SAGE_HOST}  #key=name, value=hostname
        self.savedSageHosts = []
        self.disableCorners = 1
        self.captureEdge = RIGHT_EDGE

        try:
            f = open("config", "r")
            for line in f:
                if line.startswith("ptrColor"):
                    c = line.replace("ptrColor ", "").strip().split()
                    self.ptrColor = wx.Colour( int(c[0]), int(c[1]), int(c[2]) )

                elif line.startswith("ptrLabel"):
                    self.ptrLabel = line.replace("ptrLabel ", "").strip()

                elif line.startswith("captureEdge"):
                    self.captureEdge = int(line.replace("captureEdge ", "").strip())

                elif line.startswith("disableCorners"):
                    self.disableCorners = int(line.replace("disableCorners ", "").strip())

                elif line.startswith("sageHost"):
                    self.sageHost = line.replace("sageHost ", "").strip()

                elif line.startswith("savedSageHosts"):
                    self.savedSageHosts = line.replace("savedSageHosts ", "").split()

                elif line.startswith("sharedSageHost"):
                    tokens = line.replace("sharedSageHost ", "").strip().rsplit(" ",1)
                    self.sharedSageHosts[tokens[0]] = tokens[1]

                elif line.startswith("allowSharing"):
                    doShare = line.replace("allowSharing ", "").strip()
                    global ALLOW_SHARED
                    ALLOW_SHARED = bool( int(doShare) )

                elif line.startswith("sharevnc"):
                    doShare = line.replace("sharevnc ", "").strip()
                    global SHARE_VNC
                    SHARE_VNC = bool( int(doShare) )

 
            f.close()
            
            if not self.sageHost in self.savedSageHosts:
                self.savedSageHosts.append(self.sageHost)

        except:
            #print "Unable to read the config file... creating a default config"
            l = wx.GetTextFromUser("Connect to SAGE host (IP or hostname)", "First time connecting", DEFAULT_SAGE_HOST, centre=False)
            if not l:
                l = DEFAULT_SAGE_HOST
            self.sageHost = l
            if not self.sageHost in self.savedSageHosts:
                self.savedSageHosts.append(self.sageHost)

            #tb.print_exc()
            self.save()

    
    def save(self):
        try:
            f = open("config", "w")
            ptrStr = "ptrColor %d %d %d\n" % (self.ptrColor.Red(), self.ptrColor.Green(), self.ptrColor.Blue())
            f.write(ptrStr)
            f.write("ptrLabel " + self.ptrLabel + "\n")
            f.write("sageHost " + self.sageHost + "\n")
            f.write("captureEdge " + str(self.captureEdge) + "\n")
            f.write("disableCorners " + str(self.disableCorners) + "\n")
            f.write("savedSageHosts ")
            for host in self.savedSageHosts:
                f.write(host + " ")
            f.write("\n")
            f.write("allowSharing " + str( int(ALLOW_SHARED) ) + "\n")
            for name, host in self.sharedSageHosts.iteritems():
                f.write("sharedSageHost " + name + " " + host + "\n")
            f.write("sharevnc " + str( int(SHARE_VNC) ) + "\n")
            f.close()
        except:
            print "Unable to write the config file"
            tb.print_exc()


def writeLog(logObj, arg, linesWritten):
    if logObj == None:
        return

    # first assemble the data into a string
    logLine = "[ "+str(time.time())+" ]  " + str(arg)
    #print logLine

    # write the data
    #logLine += "\n"
    logObj.write(logLine)
    linesWritten += 1

    # flush the data every 20 lines just in case...
    if linesWritten % 20 == 0:
        logObj.flush()
        os.fsync(logObj.fileno())
        linesWritten = 0


# ====================   MAC  ==================================

def readFromMacCapture():
    global mCapture

    # so we can release the click if 
    CLICK_MSG = 2
    BTN_DOWN = 1
    BTN_UP = 0
    lastClickInfo = None
    #filename = getpass.getuser() + "_pointer_log_"+time.strftime("%Y%m%d-%H%M%S", time.localtime())+".log"
    #logObj = open(filename, "a")
    #s = "==="*20
    #logObj.write(s+"\nLOG TIME: "+time.asctime()+"\n"+str(time.time())+"\n"+s+"\n")
    #linesWritten = 0 
    #logObj.flush()
    countLag = 0 

    while DO_RUN:

        # read from macCapture process which sends captured mouse events
        if not mCapture: 
            time.sleep(0.2)
            continue
    
        msg = mCapture.stdout.readline()
        if not msg:
            time.sleep(0.01)
        else:
            tokens = msg.split(" ")
            if int(tokens[0]) == CAPTURED:
                wx.CallAfter(onPtrCaptured)
                #writeLog(logObj, msg, linesWritten)
            elif int(tokens[0]) == RELEASED:
                # release the button before we release the pointer
                if lastClickInfo and int(lastClickInfo[2]) == BTN_DOWN:
                    sendMsg(lastClickInfo[0]+" "+lastClickInfo[1]+" "+str(BTN_UP))
                    lastClickInfo = None
                wx.CallAfter(onPtrReleased)
                #writeLog(logObj, msg, linesWritten)
            elif int(tokens[0]) == HAS_DOUBLE_CLICK:
                sendInitialMsg(msg)
                #writeLog(logObj, msg, linesWritten)
            else:
                tokens = msg.split()
                if int(tokens[0]) == CLICK_MSG:  # CLICK
                    lastClickInfo = tokens
                    #writeLog(logObj, msg, linesWritten)
                elif int(tokens[0]) == 1:  # MOVE 
                    if countLag == 2: 
                        #writeLog(logObj, msg, linesWritten)
                        countLag = 0
                    else :
                        countLag += 1 
                        continue
                sendMsg(msg)

    print "close is called"
    #logObj.flush()
    #os.fsync(logObj.fileno())
    #logObj.close()


    # watches a folder for new screenshots and offers to send them to SAGE
def monitorScreenshots():

    newScreenshots = []
    screenshotsAtStartup = []
    uploadedScreenshots = []

    # load the existing ones...
    for f in os.listdir(SCREENSHOT_DIR):
        if f.startswith(SCREENSHOT_NAME):
            screenshotsAtStartup.append(f)

    while DO_RUN:

        # monitor the screenshot folder (most likely the Desktop) for 
        # screenshots and offer to upload them
        for f in os.listdir(SCREENSHOT_DIR):
            if f.startswith(SCREENSHOT_NAME) and \
                    f not in uploadedScreenshots and \
                    f not in screenshotsAtStartup:
                newScreenshots.append( opj(SCREENSHOT_DIR,f) )
                uploadedScreenshots.append(f)
                
        if len(newScreenshots) > 0:
            wx.CallAfter(frame.offerScreenshotUpload, newScreenshots)
            newScreenshots = []

        time.sleep(0.5)


def startMacCapture():
    global mCapture, SCREENSHOT_DIR, SCREENSHOT_NAME
    mCapture = sp.Popen(["./macCapture"], bufsize=1, stdin=sp.PIPE, stdout=sp.PIPE)
                
    SCREENSHOT_DIR = opj(os.path.expanduser("~"), "Desktop")
    SCREENSHOT_NAME = "Screen shot"

    # start the thread that will read from the capture process through a pipe
    t = Thread(target=readFromMacCapture)
    t.start()

    # start the thread that will monitor a folder for new screenshots
    t2 = Thread(target=monitorScreenshots)
    t2.start()




# ====================   WINDOWS  ==================================


def readFromWinCapture():
    global winCapturePipe

    # so we can release the click if 
    CLICK_MSG = 2
    BTN_DOWN = 1
    BTN_UP = 0
    lastClickInfo = None
    #filename = getpass.getuser() + "_pointer_log_"+time.strftime("%Y%m%d-%H%M%S", time.localtime())+".log"
    #logObj = open(filename, "a")
    #s = "==="*20
    #logObj.write(s+"\nLOG TIME: "+time.asctime()+"\n"+str(time.time())+"\n"+s+"\n")
    #linesWritten = 0 
    #logObj.flush()

    while DO_RUN:
        
        try:
            msg = winCapturePipe.recv()
            if not msg:
                time.sleep(0.01)
            else:
                #writeLog(logObj, msg, linesWritten)
                msgId = int(msg.strip().split(' ')[0])
                if msgId == CAPTURED:
                    wx.CallAfter(onPtrCaptured)
                elif msgId == RELEASED:
                    # release the button before we release the pointer
                    if lastClickInfo and int(lastClickInfo[2]) == BTN_DOWN:
                        sendMsg(lastClickInfo[0]+" "+lastClickInfo[1]+" "+str(BTN_UP))
                        lastClickInfo = None
                    wx.CallAfter(onPtrReleased)
                elif msgId == HAS_DOUBLE_CLICK:
                    sendInitialMsg(msg)
                elif msgId == QUIT:
                    break
                else:
                    tokens = msg.split()
                    if int(tokens[0]) == CLICK_MSG:  # CLICK
                        lastClickInfo = tokens
                    sendMsg(msg)

        except:
            tb.print_exc()
            wx.CallAfter(stop)
            break

    #print "close is called"
    #logObj.flush()
    #os.fsync(logObj.fileno())
    #logObj.close()


def startWinCapture():
    global wCapture, winCapturePipe, SHARED_DO_RUN, SHARED_CAPTURE_EDGE, SCREENSHOT_DIR, SCREENSHOT_NAME, SHARED_DISABLE_CORNERS
    import multiprocessing
    import winCapture as wc

    SCREENSHOT_DIR = opj(os.path.expanduser("~"), "Desktop")
    SCREENSHOT_NAME = "ScreenShot"

    # get display height and width
    dispSize = wx.GetDisplaySize()
    width = dispSize.GetWidth()
    height = dispSize.GetHeight()

    # create the pipes for communication
    (winCapturePipe, guiPipe) = multiprocessing.Pipe(False)

    # shared variable for closing the winCapture
    SHARED_DO_RUN = multiprocessing.Value("i", 1)
    SHARED_CAPTURE_EDGE = multiprocessing.Value("i", config.captureEdge)
    SHARED_DISABLE_CORNERS = multiprocessing.Value("i", config.disableCorners)

    # start the winCapture in another process... send it the shared DO_RUN 
    # variable and the pipe so it can talk to the GUI
    wCapture = wc.WinCapture(guiPipe, width, height, SHARED_DO_RUN, SHARED_CAPTURE_EDGE, SHARED_DISABLE_CORNERS)
    wCapture.start()

    # start the thread that will read from the child process through a pipe
    t = Thread(target=readFromWinCapture)
    t.start()

    # start the thread that will monitor a folder for new screenshots
    t2 = Thread(target=monitorScreenshots)
    t2.start()


# ===============================================================



def main():
    global config, manager, frame

    app = MyApp(False)
    wx.InitAllImageHandlers()

    config = Config()
    if len(sys.argv) < 2:
        print "USAGE: python sagePointer.py SAGE_IP... using", config.sageHost, "as default"
        sageHost = config.sageHost
    else:
        sageHost = socket.gethostbyname(sys.argv[1])
    config.sageHost = sageHost

    # app launcher connection
    setAppLauncher(config.sageHost)
    setFileServer(config.sageHost)

    # SAGE connection
    manager = ManagerConnection(config.sageHost, 20005)


    try:

        frame = Frame()

        # start the capture processes depending on the platform
        if sys.platform.startswith("darwin"):
            startMacCapture()
        elif sys.platform.startswith("win"):
            startWinCapture()
        else:
            pass  # linux just run the gui
            
        # send the default captureEdge to the capture process 
        changeCaptureEdge()


        # check if there is a connection to SAGE... if not, ask the user to pick a different one
        time.sleep(0.5)
        if not manager.connected():
            Message("No connection to SAGE on: "+config.sageHost, "No Connection")
            wx.CallAfter(frame.onSAGEChangeMenuItem, (None))
            
        # create a window
        frame.Show()
        app.SetTopWindow(frame)
        app.MainLoop()

    except:
        tb.print_exc()

    sys.exit(0)


if __name__ == "__main__":
    import sys
    if sys.platform.startswith("win"):
        import multiprocessing
        multiprocessing.freeze_support()  # needed for py2exe 
    main()

